#include "stdafx.h"
#include "FourierTransformFactoryCuFft.h"
#include "includeFtLibCuda.h"
#include "fftutils.h"

using namespace FTLib;

class CRealToComplex_PackedFftwComplexCuFft : public IRealToComplex_PackedFftwComplex
{
private:
	CUDAFTPLANHANDLE		fftPlan;
public:
	CRealToComplex_PackedFftwComplexCuFft(CUDAFTPLANHANDLE plan) : fftPlan(plan){};

	~CRealToComplex_PackedFftwComplexCuFft();

	virtual void RealToComplex_PackedFftwComplex(const FourierTransfRealInputData2d& input, const FourierTransfComplexOutput2d& output, const FourierTransformAdditional* pAdditional);
};

class CRealToComplex_ComplexCuFft :public IRealToComplex_Complex
{
private:
	CUDAFTPLANHANDLE		fftPlan;
public:
	CRealToComplex_ComplexCuFft(CUDAFTPLANHANDLE plan) : fftPlan(plan){};

	~CRealToComplex_ComplexCuFft();

	virtual void RealToComplex_Complex(const FourierTransfRealInputData2d& input, const FourierTransfComplexOutput2d& output, const FourierTransformAdditional* pAdditional);
};

class CComplexToComplexFwd_ComplexCuFft :public IComplexToComplexFwd_Complex
{
private:
	CUDAFTPLANHANDLE		fftPlan;
public:
	CComplexToComplexFwd_ComplexCuFft(CUDAFTPLANHANDLE plan) : fftPlan(plan){};

	~CComplexToComplexFwd_ComplexCuFft();

	virtual void ComplexToComplex_Complex(const FourierTransfComplexInput2d& input, const FourierTransfComplexOutput2d& output, const FourierTransformAdditional* pAdditional);
};

class CComplexToComplexInv_ComplexCuFft :public IComplexToComplexInv_Complex
{
private:
	CUDAFTPLANHANDLE		fftPlan;
public:
	CComplexToComplexInv_ComplexCuFft(CUDAFTPLANHANDLE plan) : fftPlan(plan){};

	~CComplexToComplexInv_ComplexCuFft();

	virtual void ComplexToComplex_Complex(const FourierTransfComplexInput2d& input, const FourierTransfComplexOutput2d& output, const FourierTransformAdditional* pAdditional);
};

class CComplexHermetianToRealCuFft :public IComplexHermitianToReal
{
private:
	CUDAFTPLANHANDLE		fftPlan;
public:
	CComplexHermetianToRealCuFft(uint32_t width, uint32_t height, CUDAFTPLANHANDLE plan);

	~CComplexHermetianToRealCuFft();

	virtual void ComplexHermitianToReal(const FourierTransfComplexInput2d& input, const FourierTransfRealOutputData2d& output, const FourierTransformAdditional* pAdditional);
};
#if 0
class CCreateRealToComplex_Shifted :public IRealToComplex_Complex
{
private:
	CUDAFTPLANHANDLE		fftPlan;
public:
	CCreateRealToComplex_Shifted(CUDAFTPLANHANDLE plan) : fftPlan(plan){};

	~CCreateRealToComplex_Shifted();

	virtual void RealToComplex_Complex(const FourierTransfRealInputData2d& input, const FourierTransfComplexOutput2d& output);
};
#endif
//------------------------------------------------------------------------------

///*static*/AvailabilityCheckHelper CFourierTransformFactoryCuFft::checker;

///*static*/bool CFourierTransformFactoryCuFft::GetIsAvailable()
//{
//	if (CFourierTransformFactoryCuFft::checker.HasAlreadyBeenChecked())
//	{
//		return CFourierTransformFactoryCuFft::checker.IsAvailable();
//	}
//
//	if (FAILED(__HrLoadAllImportsForDll("FTLibCuda.dll")))
//	{
//		CFourierTransformFactoryCuFft::checker.SetAvailability(false);
//		return false;
//	}
//
//	CFourierTransformFactoryCuFft::checker.SetAvailability(cuFft_CheckAvailability());
//	return CFourierTransformFactoryCuFft::checker.IsAvailable();
//}

/*static*/ bool CFourierTransformFactoryCuFft::CanLoadLibrary()
{
	return SUCCEEDED(__HrLoadAllImportsForDll("FTLibCuda.dll"));
}

/*static*/ bool CFourierTransformFactoryCuFft::CanOperate()
{
	return cuFft_CheckAvailability();
}

/*static*/ void CFourierTransformFactoryCuFft::GetVersionString(std::wstring& str)
{
	if (!GetIsAvailable())
	{
		str = L"ERROR";
		return;
	}

	size_t length;
	cuFft_GetVersionString(nullptr, &length);
	length++;
	char* text = (char*)_malloca(length*sizeof(char));
	cuFft_GetVersionString(text, &length);

	std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
	str = converter.from_bytes(text);
}

CFourierTransformFactoryCuFft::CFourierTransformFactoryCuFft(const std::wstring& initializationString)
{
}

/*virtual*/std::shared_ptr<IRealToComplex_PackedFftwComplex> CFourierTransformFactoryCuFft::CreateRealToComplexPackedFftw(const Options& opts)
{
	CUDAFTPLANHANDLE handle = cuFft_CreatePlan_2dRealToComplexPackedFftw(opts.width, opts.height, CFourierTransformFactoryCuFft::CalculateNormalization(opts));
	return std::make_shared<CRealToComplex_PackedFftwComplexCuFft>(handle);
}

///*virtual*/std::shared_ptr<IRealToComplex_PackedIppComplex> CFourierTransformFactoryCuFft::CreateRealToComplexPackedIpp(const Options& opts)
//{
//	// not implemented
//	return std::shared_ptr<IRealToComplex_PackedIppComplex>();
//}

/*virtual*/std::shared_ptr<IRealToComplex_Complex> CFourierTransformFactoryCuFft::CreateRealToComplex(const Options& opts)
{

	CUDAFTPLANHANDLE handle = cuFft_CreatePlan_2dRealToComplex(opts.width, opts.height, CFourierTransformFactoryCuFft::CalculateNormalization(opts));
	return std::make_shared<CRealToComplex_ComplexCuFft>(handle);
}

/*virtual*/std::shared_ptr<IRealToComplex_Complex> CFourierTransformFactoryCuFft::CreateRealToComplex_Shifted(const Options& opts)
{
	throw std::logic_error("");
	/*if (opts.width % 2 != 0 || opts.height % 2 != 0)
	{
	throw std::runtime_error("Only implemented for even width and height.");
	}

	CUDAFTPLANHANDLE handle = cuFft_CreatePlan_2dRealToComplex_ZeroFreqInMiddle(opts.width, opts.height);
	return std::make_shared<CCreateRealToComplex_Shifted>(handle);*/
}

/*virtual*/std::shared_ptr<IComplexToComplexFwd_Complex> CFourierTransformFactoryCuFft::CreateComplexToComplexFwd(const Options& opts)
{
	CUDAFTPLANHANDLE handle = cuFft_CreatePlan_2dComplexToComplex(opts.width, opts.height, CFourierTransformFactoryCuFft::CalculateNormalization(opts));
	return std::make_shared<CComplexToComplexFwd_ComplexCuFft>(handle);
}

/*virtual*/std::shared_ptr<FTLib::IComplexToComplexInv_Complex> CFourierTransformFactoryCuFft::CreateComplexToComplexInv(const Options& opts)
{
	CUDAFTPLANHANDLE handle = cuFft_CreatePlan_2dComplexToComplex(opts.width, opts.height, CFourierTransformFactoryCuFft::CalculateNormalization(opts));
	return std::make_shared<CComplexToComplexInv_ComplexCuFft>(handle);
}

/*virtual*/std::shared_ptr<FTLib::IComplexHermitianToReal> CFourierTransformFactoryCuFft::CreateComplexHermitianToReal(const Options& opts)
{
	CUDAFTPLANHANDLE handle = cuFft_CreatePlan_2dHermetianToReal(opts.width, opts.height, CFourierTransformFactoryCuFft::CalculateNormalization(opts));
	return std::make_shared<CComplexHermetianToRealCuFft>(opts.width, opts.height, handle);
}

/*static*/double CFourierTransformFactoryCuFft::CalculateNormalization(const Options& opts)
{
	if (IsNormalizationFactorDivBySize(opts.normalizationFactor))
	{
		return opts.height*opts.width;
	}
	else if (IsNormalizationFactorDivBySqrtSize(opts.normalizationFactor))
	{
		return sqrt(opts.height*opts.width);
	}

	return opts.normalizationFactor;
}

//------------------------------------------------------------------------------
CRealToComplex_PackedFftwComplexCuFft::~CRealToComplex_PackedFftwComplexCuFft()
{
	cuFft_DeletePlan(this->fftPlan);
}

void CRealToComplex_PackedFftwComplexCuFft::RealToComplex_PackedFftwComplex(const FourierTransfRealInputData2d& input, const FourierTransfComplexOutput2d& output, const FourierTransformAdditional* pAdditional)
{
	cuFft_2d_RealToComplexPackedFftw(this->fftPlan, input, output, pAdditional != nullptr ? pAdditional->perfData : nullptr);
}

//------------------------------------------------------------------------------
CRealToComplex_ComplexCuFft::~CRealToComplex_ComplexCuFft()
{
	cuFft_DeletePlan(this->fftPlan);
}

void CRealToComplex_ComplexCuFft::RealToComplex_Complex(const FourierTransfRealInputData2d& input, const FourierTransfComplexOutput2d& output, const FourierTransformAdditional* pAdditional)
{
	cuFft_2d_RealToComplex(this->fftPlan, input, output, pAdditional == nullptr ? nullptr : pAdditional->perfData);
}

//------------------------------------------------------------------------------
CComplexToComplexFwd_ComplexCuFft::~CComplexToComplexFwd_ComplexCuFft()
{
	cuFft_DeletePlan(this->fftPlan);
}

/*virtual*/void CComplexToComplexFwd_ComplexCuFft::ComplexToComplex_Complex(const FourierTransfComplexInput2d& input, const FourierTransfComplexOutput2d& output, const FourierTransformAdditional* pAdditional)
{
	cuFft_2d_ComplexToComplex(this->fftPlan, input, output, true, pAdditional == nullptr ? nullptr : pAdditional->perfData);
}

//------------------------------------------------------------------------------
CComplexToComplexInv_ComplexCuFft::~CComplexToComplexInv_ComplexCuFft()
{
	cuFft_DeletePlan(this->fftPlan);
}

/*virtual*/void CComplexToComplexInv_ComplexCuFft::ComplexToComplex_Complex(const FourierTransfComplexInput2d& input, const FourierTransfComplexOutput2d& output, const FourierTransformAdditional* pAdditional)
{
	cuFft_2d_ComplexToComplex(this->fftPlan, input, output, false, pAdditional == nullptr ? nullptr : pAdditional->perfData);
}

//------------------------------------------------------------------------------
CComplexHermetianToRealCuFft::CComplexHermetianToRealCuFft(uint32_t width, uint32_t height, CUDAFTPLANHANDLE plan) : fftPlan(plan)
{}

CComplexHermetianToRealCuFft::~CComplexHermetianToRealCuFft()
{
	cuFft_DeletePlan(this->fftPlan);
}

/*virtual*/void CComplexHermetianToRealCuFft::ComplexHermitianToReal(const FourierTransfComplexInput2d& input, const FourierTransfRealOutputData2d& output, const FourierTransformAdditional* pAdditional)
{
	FourierTransfComplexInput2d inputCuFft;
	inputCuFft.width = (input.width / 2 + 1);
	inputCuFft.height = input.height;
	inputCuFft.pitch = input.pitch;
	inputCuFft.ptrData = input.ptrData;
	cuFft_2d_HermetianToReal(this->fftPlan, inputCuFft, output, pAdditional == nullptr ? nullptr : pAdditional->perfData);
}

#if 0
//------------------------------------------------------------------------------
CCreateRealToComplex_Shifted::~CCreateRealToComplex_Shifted()
{
	cuFft_DeletePlan(this->fftPlan);
}

void CCreateRealToComplex_Shifted::RealToComplex_Complex(const FourierTransfRealInputData2d& input, const FourierTransfComplexOutput2d& output)
{
	cuFft_2d_RealToComplex_ZeroFreqInMiddle(this->fftPlan, input, output);
}
#endif
